前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 原生koa2实现jsonp

30 秒速记

  • JSONP = 利用 <script> 不受同源策略限制,服务端返回 cb({...}) 这段 JS,浏览器执行时把数据交给前端定义好的 cb
  • 只能 GET,不能带自定义请求头,出错了前端也拿不到状态码
  • callback 名必须校验,比如 /^[\w$.]{1,64}$/,原样拼进响应就是 XSS
  • 浏览器会带上目标站的 Cookie,敏感数据走 JSONP 等于让任何网站都能读
  • 新项目用 CORS 或同源代理,JSONP 只留给老页面兼容

JSONP 说白了就是借 <script> 标签能跨域加载脚本的特性,让服务端把数据包成一段函数调用返回。 前端先在全局定义好一个函数,比如 handleData,再插一个 <script src="/api?callback=handleData">;服务端返回 handleData({"ok":true}),浏览器一执行,数据就进了这个函数。在 Koa2 里原生实现只要几行:读 ctx.query.callback,JSON.stringify 数据,拼起来,ctx.type 设成 text/javascript。它的硬伤是只能 GET、错误处理很弱,而且回调名不校验就是注入点,所以现在只在兼容老页面时用。

理解「原生koa2实现jsonp」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

JSONP 的完整链路可以拆成前后端两半来看。

前端要做三件事:注册一个全局回调、插入 <script>、收尾清理。

function jsonp(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    const cbName = 'jsonp_' + Date.now() + '_' + Math.random().toString(36).slice(2)
    const script = document.createElement('script')
    const timer = setTimeout(() => cleanup(reject, new Error('timeout')), timeout)

    function cleanup(fn, value) {
      clearTimeout(timer)
      delete window[cbName]
      script.remove()
      fn(value)
    }

    window[cbName] = data => cleanup(resolve, data)        // 服务端返回的脚本会调用它
    script.onerror = () => cleanup(reject, new Error('load error'))
    script.src = `${url}?callback=${cbName}`
    document.body.appendChild(script)
  })
}

jsonp('http://localhost:3000/getData.jsonp').then(console.log)
// => { success: true, data: { text: 'this is a jsonp api', time: ... } }
@前端进阶之旅: 代码已经复制到剪贴板

每次请求用一个唯一的回调名,并发多个请求也不会串数据;成功、失败、超时都要删掉全局函数和 script 节点。

后端要做的是把数据包成一段合法的 JS,并守住回调名:

const CALLBACK_RE = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/

app.use(async (ctx, next) => {
  if (ctx.method !== 'GET' || ctx.path !== '/getData.jsonp') return next()

  const cb = ctx.query.callback || 'callback'
  if (cb.length > 64 || !CALLBACK_RE.test(cb)) {
    ctx.status = 400
    ctx.body = 'invalid callback'
    return
  }
  ctx.type = 'text/javascript'
  ctx.set('X-Content-Type-Options', 'nosniff')
  ctx.body = `/**/ ;${cb}(${JSON.stringify({ success: true })})`
})
@前端进阶之旅: 代码已经复制到剪贴板

开头的 ; 是为了防止前面有别的脚本没写分号,拼起来变成函数调用;/**/ 是历史上防御 Rosetta Flash 攻击留下的习惯写法。跨域能力来自 <script> 标签本身,text/javascript 只是让浏览器认它是脚本;配上 nosniff 之后,类型不对的响应浏览器会直接拒绝执行。

面试官追问

追问 1JSONP 为什么能跨域,fetch 却不行?
参考回答

同源策略限制的是脚本读取跨域响应,而 <script>、<img> 这类标签本来就允许加载跨域资源。JSONP 不是去「读」响应,而是让响应自己作为脚本执行,把数据塞给你提前定义的函数。

追问 2请求 ?callback=alert(document.cookie)//,服务端原样拼接会怎样?
参考回答

响应变成 alert(document.cookie)//({...}),这就是一段任意脚本,在调用方页面里执行。回调名必须用白名单正则校验,比如 /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/,不合法直接返回 400。

追问 3用户资料接口能不能做成 JSONP?
参考回答

不能。<script> 请求会自动带上目标站的 Cookie,恶意网站插一个 <script src="你的接口?callback=steal">,就能拿到当前登录用户的资料。这叫 JSONP 劫持,带身份的数据只能走配好白名单的 CORS。

追问 4JSONP 请求失败了,前端怎么知道?
参考回答

拿不到状态码,只能监听 script.onerror,再加一个超时定时器兜底,比如 5s 还没回调就当失败。服务端返回 500 时 onerror 会触发,但具体错误信息是拿不到的。

追问 5新接口还要不要支持 JSONP?
参考回答

没有老页面依赖就不加。CORS 支持所有方法、能拿状态码、数据不会被当代码执行,配好 Access-Control-Allow-Origin 白名单就行。

# 原生koa2实现jsonp

# 前言

30 秒速记

  • 前端跨域拿数据,服务端得提供跨域接口,老项目里最常见的做法就是 JSONP
  • 原理:<script> 能跨域加载,服务端返回 cb(data),浏览器执行时把数据交给 cb
  • 局限:只有 GET、拿不到状态码、回调名要防注入、带 Cookie 的敏感数据不能走
  • 现在的首选是 CORS;调用方是服务端或 App 时根本不存在跨域问题,直接给 JSON

跨域取数据这件事,早些年最常见的解法就是 JSONP,这一章要讲的就是在 Koa2 里怎么提供这种接口。 浏览器的同源策略不让 XHR 读别的域的响应,但 <script> 标签可以随便加载跨域脚本,JSONP 就钻了这个空子:让服务端返回一段调用前端函数的 JS。它在 IE8、IE9 不支持完整 CORS 的年代很实用,现在浏览器都支持 CORS 了,新接口我基本不会再提供 JSONP,只有要兼容老页面时才保留。

在项目复杂的业务场景,有时候需要在前端跨域获取数据,这时候提供数据的服务就需要提供跨域请求的接口,通常是使用JSONP的方式提供跨域接口。

理解「前言」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1把普通 JSON 接口的响应类型改成 text/javascript,算不算实现了 JSONP?
参考回答

不算。浏览器执行 {"price":99} 这段脚本什么也不会发生,数据进不了你的代码。关键是服务端要把数据包成 render({"price":99}),并且页面提前定义好 render。

追问 2移动端 App 也要调这个接口,需要做成 JSONP 吗?
参考回答

不需要。跨域是浏览器的限制,App 原生请求、服务端请求都没有同源策略,直接返回 JSON 最好。JSONP 只该给依赖 <script> 加载的老网页用。

追问 3现在新写一个跨域接口,你会选 JSONP 还是 CORS?
参考回答

CORS。支持 POST、能带自定义头、前端能拿到状态码,数据也不会被当成代码执行。服务端配好 Access-Control-Allow-Origin 白名单就行,Koa 里用 @koa/cors 一行搞定。

追问 4
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

原生koa2实现jsonp|Nodejs专题系列

# 原生koa2实现jsonp

30 秒速记

  • JSONP = 利用 <script> 不受同源策略限制,服务端返回 cb({...}) 这段 JS,浏览器执行时把数据交给前端定义好的 cb
  • 只能 GET,不能带自定义请求头,出错了前端也拿不到状态码
  • callback 名必须校验,比如 /^[\w$.]{1,64}$/,原样拼进响应就是 XSS
  • 浏览器会带上目标站的 Cookie,敏感数据走 JSONP 等于让任何网站都能读
  • 新项目用 CORS 或同源代理,JSONP 只留给老页面兼容

JSONP 说白了就是借 <script> 标签能跨域加载脚本的特性,让服务端把数据包成一段函数调用返回。 前端先在全局定义好一个函数,比如 handleData,再插一个 <script src="/api?callback=handleData">;服务端返回 handleData({"ok":true}),浏览器一执行,数据就进了这个函数。在 Koa2 里原生实现只要几行:读 ctx.query.callback,JSON.stringify 数据,拼起来,ctx.type 设成 text/javascript。它的硬伤是只能 GET、错误处理很弱,而且回调名不校验就是注入点,所以现在只在兼容老页面时用。

理解「原生koa2实现jsonp」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

JSONP 的完整链路可以拆成前后端两半来看。

前端要做三件事:注册一个全局回调、插入 <script>、收尾清理。

function jsonp(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    const cbName = 'jsonp_' + Date.now() + '_' + Math.random().toString(36).slice(2)
    const script = document.createElement('script')
    const timer = setTimeout(() => cleanup(reject, new Error('timeout')), timeout)

    function cleanup(fn, value) {
      clearTimeout(timer)
      delete window[cbName]
      script.remove()
      fn(value)
    }

    window[cbName] = data => cleanup(resolve, data)        // 服务端返回的脚本会调用它
    script.onerror = () => cleanup(reject, new Error('load error'))
    script.src = `${url}?callback=${cbName}`
    document.body.appendChild(script)
  })
}

jsonp('http://localhost:3000/getData.jsonp').then(console.log)
// => { success: true, data: { text: 'this is a jsonp api', time: ... } }
@前端进阶之旅: 代码已经复制到剪贴板

每次请求用一个唯一的回调名,并发多个请求也不会串数据;成功、失败、超时都要删掉全局函数和 script 节点。

后端要做的是把数据包成一段合法的 JS,并守住回调名:

const CALLBACK_RE = /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/

app.use(async (ctx, next) => {
  if (ctx.method !== 'GET' || ctx.path !== '/getData.jsonp') return next()

  const cb = ctx.query.callback || 'callback'
  if (cb.length > 64 || !CALLBACK_RE.test(cb)) {
    ctx.status = 400
    ctx.body = 'invalid callback'
    return
  }
  ctx.type = 'text/javascript'
  ctx.set('X-Content-Type-Options', 'nosniff')
  ctx.body = `/**/ ;${cb}(${JSON.stringify({ success: true })})`
})
@前端进阶之旅: 代码已经复制到剪贴板

开头的 ; 是为了防止前面有别的脚本没写分号,拼起来变成函数调用;/**/ 是历史上防御 Rosetta Flash 攻击留下的习惯写法。跨域能力来自 <script> 标签本身,text/javascript 只是让浏览器认它是脚本;配上 nosniff 之后,类型不对的响应浏览器会直接拒绝执行。

面试官追问

追问 1JSONP 为什么能跨域,fetch 却不行?
参考回答

同源策略限制的是脚本读取跨域响应,而 <script>、<img> 这类标签本来就允许加载跨域资源。JSONP 不是去「读」响应,而是让响应自己作为脚本执行,把数据塞给你提前定义的函数。

追问 2请求 ?callback=alert(document.cookie)//,服务端原样拼接会怎样?
参考回答

响应变成 alert(document.cookie)//({...}),这就是一段任意脚本,在调用方页面里执行。回调名必须用白名单正则校验,比如 /^[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*$/,不合法直接返回 400。

追问 3用户资料接口能不能做成 JSONP?
参考回答

不能。<script> 请求会自动带上目标站的 Cookie,恶意网站插一个 <script src="你的接口?callback=steal">,就能拿到当前登录用户的资料。这叫 JSONP 劫持,带身份的数据只能走配好白名单的 CORS。

追问 4JSONP 请求失败了,前端怎么知道?
参考回答

拿不到状态码,只能监听 script.onerror,再加一个超时定时器兜底,比如 5s 还没回调就当失败。服务端返回 500 时 onerror 会触发,但具体错误信息是拿不到的。

追问 5新接口还要不要支持 JSONP?
参考回答

没有老页面依赖就不加。CORS 支持所有方法、能拿状态码、数据不会被当代码执行,配好 Access-Control-Allow-Origin 白名单就行。

# 原生koa2实现jsonp

# 前言

30 秒速记

  • 前端跨域拿数据,服务端得提供跨域接口,老项目里最常见的做法就是 JSONP
  • 原理:<script> 能跨域加载,服务端返回 cb(data),浏览器执行时把数据交给 cb
  • 局限:只有 GET、拿不到状态码、回调名要防注入、带 Cookie 的敏感数据不能走
  • 现在的首选是 CORS;调用方是服务端或 App 时根本不存在跨域问题,直接给 JSON

跨域取数据这件事,早些年最常见的解法就是 JSONP,这一章要讲的就是在 Koa2 里怎么提供这种接口。 浏览器的同源策略不让 XHR 读别的域的响应,但 <script> 标签可以随便加载跨域脚本,JSONP 就钻了这个空子:让服务端返回一段调用前端函数的 JS。它在 IE8、IE9 不支持完整 CORS 的年代很实用,现在浏览器都支持 CORS 了,新接口我基本不会再提供 JSONP,只有要兼容老页面时才保留。

在项目复杂的业务场景,有时候需要在前端跨域获取数据,这时候提供数据的服务就需要提供跨域请求的接口,通常是使用JSONP的方式提供跨域接口。

理解「前言」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1把普通 JSON 接口的响应类型改成 text/javascript,算不算实现了 JSONP?
参考回答

不算。浏览器执行 {"price":99} 这段脚本什么也不会发生,数据进不了你的代码。关键是服务端要把数据包成 render({"price":99}),并且页面提前定义好 render。

追问 2移动端 App 也要调这个接口,需要做成 JSONP 吗?
参考回答

不需要。跨域是浏览器的限制,App 原生请求、服务端请求都没有同源策略,直接返回 JSON 最好。JSONP 只该给依赖 <script> 加载的老网页用。

追问 3现在新写一个跨域接口,你会选 JSONP 还是 CORS?
参考回答

CORS。支持 POST、能带自定义头、前端能拿到状态码,数据也不会被当成代码执行。服务端配好 Access-Control-Allow-Origin 白名单就行,Koa 里用 @koa/cors 一行搞定。

追问 4页面白屏了,网络面板里 JSONP 请求是 200,接下来看什么?
参考回答

看响应体是不是 cb(...) 的形式、回调名和页面里定义的是否一致,再看控制台有没有 xxx is not defined 或语法错误。200 只说明脚本下载成功,执行有没有出错是另一回事。

# 实现JSONP

30 秒速记

  • 服务端只做三件事:读 ctx.query.callback → JSON.stringify(data) → 拼成 ;cb(...) 写进 ctx.body
  • ctx.type = 'text/javascript',让浏览器把响应当脚本执行
  • 路由判断用 ctx.method === 'GET' + ctx.path,没命中要 await next() 交给后面的中间件
  • 数据必须走 JSON.stringify,回调名必须单独校验,两者都不能省

原生实现 JSONP 不需要任何第三方库,一个中间件判断路径、包装响应就够了。 核心逻辑就是把 ctx.query.callback 拿到的回调名和 JSON.stringify 后的数据拼成 ;callback({...}),设置 ctx.type 为 text/javascript 返回。前端那边用 <script src="http://localhost:3000/getData.jsonp?callback=fn"> 去加载,fn 就会收到数据。接到真实服务里要注意两点:只拦自己那条路径,其他请求放行给后面的中间件;回调名要校验,数据序列化不能偷懒。

app.use(async (ctx, next) => {
  if (ctx.method !== 'GET' || ctx.path !== '/getData.jsonp') return next()
  const cb = ctx.query.callback || 'callback'
  ctx.type = 'text/javascript'
  ctx.body = `;${cb}(${JSON.stringify({ success: true })})`
})
// 访问 /getData.jsonp?callback=fn => ;fn({"success":true})
@前端进阶之旅: 代码已经复制到剪贴板

← 8.3 项目建表初始化9.2 koa jsonp中间件 →

fe